var net = require('net');
var http = require('http');
var url = require('url');
var fs = require('fs');
var path = require('path')

var doReadFile = function(filename){
  var finalPath = path.join(process.cwd(),filename);
  if(fs.existsSync(finalPath) && fs.statSync(finalPath).isFile()){
    var cont = fs.readFileSync(finalPath,{encoding:'utf-8'});
    try{
      return JSON.parse(cont);
    }catch(e){}
    return cont;
  }
  return null;
}
var cfgFileName = 'config.json';
var fileCfg = doReadFile(cfgFileName) || {};
var clientList = ["::ffff:127.0.0.1","127.0.0.1"];
var setClientList = function(fileCfg){
  if(!fileCfg) fileCfg = {};
  var list = fileCfg.clientList;
  if(list && (typeof list) == 'object'){
    console.log('clientList set to:'+list);
    clientList = list;
  }
}
setClientList(fileCfg);
var checkClient = function(addr){
  if(!addr) return false;
  for(var l of clientList){
    if(addr == l || addr.endsWith(":"+l)){
      return true;
    }
  }
  return false;
}
var blackSiteList = [];
var setBlackSiteList = function(fileCfg){
  if(!fileCfg) fileCfg = {};
  var list = fileCfg.blackSiteList;
  if(list && (typeof list) == 'object'){
    console.log('blackSiteList set to:'+list);
    blackSiteList = list;
  }
}
setBlackSiteList(fileCfg);
var checkTargetSite = function(addr){
  if(!addr) return true;
  for(var l of blackSiteList){
    if(addr == l || addr.startsWith(l+":")){
      return false;
    }
  }
  return true;
}
var proxyServer = http.createServer(httpOptions);

// handle http proxy requests
function httpOptions(clientReq, clientRes) {
  if(!checkClient(clientReq.socket.remoteAddress)){
    console.error(clientReq.socket.remoteAddress + ' are not allowed!');
    clientRes.writeHead(401, { 'Content-Type': 'text/plain;charset=utf-8'});
    clientRes.write('Client not allowed ['+clientReq.socket.remoteAddress+']');
    clientRes.end();
    return;
  }
  var reqUrl = url.parse(clientReq.url);
  if(!checkTargetSite(reqUrl.host)){
    console.error(clientReq.socket.remoteAddress + ' access: ' + reqUrl.host + ' are not allowed!');
    clientRes.writeHead(403, { 'Content-Type': 'text/plain;charset=utf-8'});
    clientRes.write('Target site not allowed ['+reqUrl.host+']');
    clientRes.end();
    return;
  }
  console.log(new Date().toLocaleString()+' '+clientReq.socket.remoteAddress + ' proxy for http request: ' + reqUrl.href);

  var options = {
    hostname: reqUrl.hostname,
    port: reqUrl.port,
    path: reqUrl.path,
    method: clientReq.method,
    headers: clientReq.headers
  };

  // create socket connection on behalf of client, then pipe the response to client response (pass it on)
  var serverConnection = http.request(options, function (res) {
    clientRes.writeHead(res.statusCode, res.headers)
    res.pipe(clientRes);
  });

  clientReq.pipe(serverConnection);

  clientReq.on('error', (e) => {
    console.log('client socket error: ' + e);
  });

  serverConnection.on('error', (e) => {
    console.log('server connection error: ' + e);
  });
}

// handle https proxy requests (CONNECT method)
proxyServer.on('connect', (clientReq, clientSocket, head) => {
  if(!checkClient(clientSocket.remoteAddress)){
    console.error(clientSocket.remoteAddress + ' are not allowed!');
    clientSocket.end();
    return;
  }
  var reqUrl = url.parse('https://' + clientReq.url);
  if(!checkTargetSite(reqUrl.host)){
    console.error(clientSocket.remoteAddress + ' access: '+reqUrl.host + ' are not allowed!');
    clientSocket.end();
    return;
  }
  console.log(new Date().toLocaleString()+' '+clientSocket.remoteAddress + ' proxy for https request: ' + reqUrl.href + '(path encrypted by ssl)');

  var options = {
    port: reqUrl.port == null ? 443 : reqUrl.port,
    host: reqUrl.hostname
  };

  // create socket connection for client, then pipe (redirect) it to client socket
  var serverSocket = net.connect(options, () => {
    clientSocket.write('HTTP/' + clientReq.httpVersion + ' 200 Connection Established\r\n' +
                  'Proxy-agent: Node.js-Proxy\r\n' +
                  '\r\n', 'UTF-8', () => {
      // creating pipes in both ends
      serverSocket.write(head);
      serverSocket.pipe(clientSocket);
      clientSocket.pipe(serverSocket);
    });
  });

  clientSocket.on('error', (e) => {
    console.log("client socket error: " + e);
    serverSocket.end();
  });

  serverSocket.on('error', (e) => {
    console.log("forward proxy server connection error: " + e);
    clientSocket.end();
  });
});

proxyServer.on('clientError', (err, clientSocket) => {
  console.log('client error: ' + err);
  clientSocket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

proxyServer.on('close',_ => {
  console.log('forward proxy server closed!');
});

var doStopSvr = function(){
  return new Promise((res,rej) => {
    if(proxyServer.listening){
      console.log('forward proxy server is stopping!');
      proxyServer.close(_ => {
        res();
      });
    }else{
      res();
    }
  });
}

var doStartSvr = function(fileCfg){
  if(fileCfg && fileCfg.serverPort){
    var port = parseInt(process.env.server_port) || fileCfg.serverPort || 2560;
    if(isNaN(port)){
      console.error('illegal serverPort',port);
      return;
    }
    if(doStartSvr.port != port){
      doStopSvr().then(_ => {
        proxyServer.listen(port);
        doStartSvr.port = port;
        console.log('forward proxy server started, listening on port '+port);
      });
    }
  }else{
    console.error('illegal fileCfg',fileCfg);
  }
}
doStartSvr(fileCfg);

var sockesProxy = require('./sockesProxy');
var doStartSockesSvr = function(fileCfg){
  console.log('doStartSockesSvr',fileCfg);
  if(fileCfg && fileCfg.sockesPort){
    var port = parseInt(process.env.sockes_port) || fileCfg.sockesPort || 2562;
    if(isNaN(port)){
      console.error('illegal sockesPort',port);
      return;
    }
    if(doStartSockesSvr.port != port){
      sockesProxy.start(port,checkClient,checkTargetSite);
      doStartSockesSvr.port = port;
    }
  }else{
    console.error('illegal fileCfg',fileCfg);
  }
};
doStartSockesSvr(fileCfg);
//process.cwd()
fs.watch(path.join(process.cwd()),null,function(eventType,filename){
  if(cfgFileName == filename){
    fileCfg = doReadFile(filename);
    setClientList(fileCfg);
    setBlackSiteList(fileCfg);
    doStartSvr(fileCfg);
    doStartSockesSvr(fileCfg);
  }
});
module.exports = proxyServer;
